from typing import List

from fastapi import HTTPException, status
from fastapi_pagination import Page, Params
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import joinedload
from sqlmodel import select, delete

from app.auth.models import UserEntity, LoginAccountEntity, WeixinAccountEntity, RoleEntity, UserRoleEntity, UserWhole, \
    Role
from app.core.database import get_async_session, fetch_first, fetch_all, dict_to_entity
from app.core.security import verify_password


class AuthService:

    def __init__(self):
        self.async_session = get_async_session

    async def get_user(self, user_id: int) -> UserEntity | None:
        async with self.async_session() as session:
            result = await UserEntity.get(session, user_id)
            return result

    async def detail_user(self, user_id: int) -> UserEntity | None:
        async with self.async_session() as session:
            query = select(UserEntity) \
                .where(UserEntity.id == user_id) \
                .options(joinedload(UserEntity.login_accounts),
                         joinedload(UserEntity.weixin_accounts),
                         joinedload(UserEntity.roles))
            result = await fetch_first(session, query)
            return result

    async def get_user_by_username(self, username: str) -> UserEntity | None:
        async with self.async_session() as session:
            query = select(UserEntity).join(LoginAccountEntity) \
                .where(LoginAccountEntity.username == username)
            result = await fetch_first(session, query)
            return result

    async def get_user_by_open_id(self, open_id: str) -> UserEntity | None:
        async with self.async_session() as session:
            query = select(UserEntity).join(WeixinAccountEntity) \
                .where(WeixinAccountEntity.open_id == open_id)
            result = await fetch_first(session, query)
            return result

    async def authc_user(self, username: str, password: str) -> UserEntity | None:
        async with self.async_session() as session:
            query = select(LoginAccountEntity) \
                .where(LoginAccountEntity.username == username)
            login_account = await fetch_first(session, query)
            if login_account and verify_password(password, login_account.hashed_password):
                user = await self.get_user(login_account.user_id)
                return user
            return None

    async def get_user_with_roles(self, user_id: int) -> UserEntity | None:
        async with self.async_session() as session:
            query = select(UserEntity) \
                .where(UserEntity.id == user_id) \
                .options(joinedload(UserEntity.roles))
            result = await fetch_first(session, query)
            return result

    async def list_users(self, params: Params = Params(), *filters) -> Page[UserEntity]:
        async with self.async_session() as session:
            result = await UserEntity.list_in_pages(session, params, *filters)
            return result

    async def save_user(self, user: UserWhole) -> UserEntity:
        async with self.async_session() as session:
            try:
                entity = dict_to_entity(user.dict(), UserEntity)
                await session.merge(entity)
                for child_entity in entity.login_accounts:
                    child_entity.user_id = user.id
                    await session.merge(child_entity)
                for child_entity in entity.weixin_accounts:
                    child_entity.user_id = user.id
                    await session.merge(child_entity)
                await session.commit()
                return entity
            except IntegrityError:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail="username已经被占用",
                )

    async def get_role(self, role_id: int) -> RoleEntity | None:
        async with self.async_session() as session:
            result = await RoleEntity.get(session, role_id)
            return result

    async def detail_role(self, role_id: int) -> RoleEntity | None:
        async with self.async_session() as session:
            query = select(RoleEntity) \
                .where(RoleEntity.id == role_id) \
                .options(joinedload(RoleEntity.permissions))
            result = await fetch_first(session, query)
            return result

    async def get_role_by_name(self, name: str) -> RoleEntity | None:
        async with self.async_session() as session:
            query = select(RoleEntity) \
                .where(RoleEntity.name == name)
            result = await fetch_first(session, query)
            return result

    async def get_all_roles(self) -> List[RoleEntity]:
        async with self.async_session() as session:
            result = await RoleEntity.get_all(session)
            return result

    async def get_roles_by_user_id(self, user_id: int) -> List[RoleEntity]:
        async with self.async_session() as session:
            query = select(RoleEntity).join(UserRoleEntity) \
                .where(UserRoleEntity.user_id == user_id)
            result = await fetch_all(session, query)
            return result

    async def save_role(self, role: Role) -> RoleEntity:
        async with self.async_session() as session:
            entity = dict_to_entity(role.dict(), RoleEntity)
            await session.merge(entity)
            try:
                await session.commit()
                return entity
            except IntegrityError as x:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail="name已经被占用",
                )

    async def grant_user_roles(self, user_id: int, *role_ids: int) -> int:
        async with self.async_session() as session:
            stmt = delete(UserRoleEntity).where(UserRoleEntity.user_id == user_id)
            await session.execute(stmt)
            user_roles = [UserRoleEntity(user_id=user_id, role_id=role_id) for role_id in role_ids]
            session.add_all(user_roles)
            await session.commit()
            return len(user_roles)

    async def delete_role(self, role_id: int) -> None:
        async with self.async_session() as session:
            stmt = delete(RoleEntity).where(RoleEntity.id == role_id)
            await session.execute(stmt)
            await session.commit()


auth_service = AuthService()
